package edu.yang.dbutils;

import edu.yang.dbutils.comment.Column;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ServiceLoader;

/**
 * 将数据库信息映射至实体类
 * @author yang
 * @date 2024-05-12
 */
public class BeanProcessor {
    /**
     * 转换器SPI
     */
    private static final ServiceLoader<TypeConverter> LOADER = ServiceLoader.load(TypeConverter.class); 

    /**
     * 通过 反射 + 内省 的方式将数据库单条数据映射至实体类对象中
     * @param rs
     * @param beanClass
     * @return
     * @param <T>
     */
    public static <T> T createBean(ResultSet rs, Class<T> beanClass) throws SQLException {
        //实例化实体对象
        T instance = newInstance(beanClass);
        //获取 beanClass 的属性描述器
        PropertyDescriptor[] beanPropertyDescriptors = propertyDescriptors(beanClass);
        //循环结果集获取列名
        for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++){
            //获取列名
            String columnLabel = rs.getMetaData().getColumnLabel(i);
            //循环解析属性描述器
            for (PropertyDescriptor pd : beanPropertyDescriptors) {
                //判断实体对象属性名与数据库列名是否匹配
                if (hasColumnLabel(columnLabel, pd, beanClass)){
                    //对属性字段进行赋值
                    processColumn(rs, pd, columnLabel, instance);
                }
            }
        }
        //返回映射后的实体对象
        return instance;
    }

    /**
     * 实例化实体对象的方法
     * @param beanClass
     * @return
     * @param <T>
     */
    private static<T> T newInstance(Class<T> beanClass){
        try {
            T instance = beanClass.getConstructor().newInstance();
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("Null instance.", e);
        }
    }

    /**
     * 获取属性描述器的方法
     * @param beanClass
     * @return beanClass 的属性数组
     */
    private static PropertyDescriptor[] propertyDescriptors(Class<?> beanClass){
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
            return beanInfo.getPropertyDescriptors();
        } catch (IntrospectionException e) {
            throw new RuntimeException("Null beanInfo.", e);
        }
    }

    /**
     * 判断实体类属性与列名是否匹配
     * @param columnLabel 列名
     * @param pd 属性描述器对象
     * @param beanClass 实体类 Class 对象
     * @return
     */
    private static boolean hasColumnLabel(String columnLabel, PropertyDescriptor pd, Class<?> beanClass){
        try {
            //获取属性字段名
            String fieldName = pd.getName();
            //获取属性字段实例
            Field field = beanClass.getDeclaredField(fieldName);
            //判断有无注解
            if (field.isAnnotationPresent(Column.class)){
                //获取注解的值
                fieldName = field.getAnnotation(Column.class).value();
            }
            //判断 属性字段名/注解的值 是否与列名相同（不区分大小写）
            return fieldName.equalsIgnoreCase(columnLabel);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("Null fieldName", e);
        }
    }

    private static void processColumn(ResultSet rs, PropertyDescriptor pd, String columnLabel, Object instance){
        try {
            //根据列名获取值
            Object value = rs.getObject(columnLabel);
            //执行类型转换(例如日期不匹配)
            if (value != null){
                //遍历 SPI 转换器
                for (TypeConverter converter : LOADER) {
                    //判断是哪种类型的实例
                    if (converter.supports(pd.getPropertyType())){
                        //进行类型转换并覆盖
                        value = converter.convert(pd.getPropertyType(), value);
                        break;
                    }
                }
            }

            //判断值是否是基本数据类型，并且为空
            if (pd.getPropertyType().isPrimitive() && value == null) {
                return;
            }
            //获取属性描述器Setter方法，并进行赋值操作
            pd.getWriteMethod().invoke(instance, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
